﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml;
using System.Globalization;
using DBWTProject.models;

namespace DBWTProject.route
{
    public class KmlParser
    {
        protected XmlDocument kml;
        protected XmlNamespaceManager NsMgr;
        protected NumberFormatInfo numberFormatUS = new CultureInfo("en-US", false).NumberFormat;
        protected IList<Tuple<double, double, double>> coordinateList;

        public KmlParser(String kmlstring)
        {
            kml = new XmlDocument();
            kml.LoadXml(kmlstring);

            NsMgr = new XmlNamespaceManager(kml.NameTable);
            NsMgr.AddNamespace("kml21", "http://earth.google.com/kml/2.1");
            NsMgr.AddNamespace("kml20", "http://earth.google.com/kml/2.0");
        }


        /// <summary>
        /// Get a list of distance differences between consecutive coordinate points
        /// </summary>
        public IList<double> GetDistanceDifference()
        {
            // Provide persistence so as not to parse the coordinate list again
            if (coordinateList == null)
            {
                coordinateList = GetCoordinateList();
            }

            // Found coordinates
            if (coordinateList != null)
            {
                IList<double> returnList = new List<double>();
                for (int x = 1; x < coordinateList.Count; ++x)
                {
                    Tuple<double, double, double> curr = coordinateList[x];
                    Tuple<double, double, double> prev = coordinateList[x - 1];

                    double prevLon = Convert.ToDouble(prev.Item1, numberFormatUS);
                    double prevLat = Convert.ToDouble(prev.Item2, numberFormatUS);
                    double currLon = Convert.ToDouble(curr.Item1, numberFormatUS);
                    double currLat = Convert.ToDouble(curr.Item2, numberFormatUS);

                    returnList.Add(DistanceAlgorithm.DistanceBetweenPlaces(prevLon, prevLat, currLon, currLat));
                }

                return returnList;
            }

            // No coordinates found
            return null;
        }

        /// <summary>
        /// Get a list of elevation differences between consecutive coordinate points
        /// </summary>
        public IList<double> GetElevationDifference()
        {
            // Provide persistence so as not to parse the coordinate list again
            if (coordinateList == null)
            {
                coordinateList = GetCoordinateList();
            }

            if (coordinateList != null)
            {
                IList<double> returnList = new List<double>();
                for (int x = 1; x < coordinateList.Count; ++x)
                {
                    Tuple<double, double, double> curr = coordinateList[x];
                    Tuple<double, double, double> prev = coordinateList[x - 1];

                    returnList.Add(Math.Abs(Convert.ToDouble(curr.Item3, numberFormatUS) - Convert.ToDouble(prev.Item3, numberFormatUS)));
                }

                return returnList;
            }

            return null;
        }


        /// <summary>
        /// Split the LineString in coordinates into individual lines while filtering empty elements
        /// </summary>
        /// <param name="lineString">Raw LineString element as a string</param>
        /// <returns>An array of individual coordinate strings</returns>
        private String[] ChopCoordLineString(String lineString)
        {
            String[] rawCoordArray = lineString.Trim().Split(" '\n\r".ToCharArray());
            return rawCoordArray.Where(item => !String.IsNullOrEmpty(item)).ToArray();
        }

        
        /// <summary>
        /// Calculate total duration using first and last placemark's TimeStamp element
        /// </summary>
        public TimeSpan GetDuration()
        {
            try
            {
                XmlNode placemark1 = kml.SelectSingleNode("//*[local-name()='Placemark'][1]/*[local-name()='TimeStamp']/*[local-name()='when']", NsMgr);
                XmlNode placemark2 = kml.SelectSingleNode("//*[local-name()='Placemark'][last()]/*[local-name()='TimeStamp']/*[local-name()='when']", NsMgr);

                if (placemark1 == null || placemark2 == null)
                {
                    placemark1 = kml.SelectSingleNode("//*[local-name()='TimeSpan'][1]/*[local-name()='begin']", NsMgr);
                    placemark2 = kml.SelectSingleNode("//*[local-name()='TimeSpan'][1]/*[local-name()='end']", NsMgr);
                    if (placemark1 == null || placemark2 == null)
                    {
                        return TimeSpan.Zero;
                    }
                }

                String beginTimeString = placemark1.InnerText;
                String endTimeString = placemark2.InnerText;

                DateTime beginDateTime = DateTime.Parse(beginTimeString);
                DateTime endDateTime = DateTime.Parse(endTimeString);

                return endDateTime - beginDateTime;
            }
            catch (Exception e)
            {
                return TimeSpan.Zero;
            }
        }

        /// <summary>
        /// Calculate mean speed using total duration and distance
        /// </summary>
        public double GetMeanSpeed()
        {
            TimeSpan timeSpan = GetDuration();

            if (timeSpan == TimeSpan.Zero)
            {
                return Double.NaN;
            }

            double distance = GetDistanceDifference().Sum();

            return distance / timeSpan.TotalHours;
        }

        public DateTime GetRouteStartDate()
        {
            XmlNode placemark1 = kml.SelectSingleNode("//*[local-name()='Placemark'][1]/*[local-name()='TimeStamp']/*[local-name()='when']", NsMgr);
            if (placemark1 == null)
            {//Fallback
                placemark1 = kml.SelectSingleNode("//*[local-name()='TimeSpan'][1]/*[local-name()='begin']", NsMgr);
            }

            return DateTime.Parse(placemark1.InnerText);
        }

        /// <summary>
        /// Get a list of tuples containing longitude, latitude and elevation
        /// </summary>
        public IList<Tuple<double, double, double>> GetCoordinateList()
        {
            XmlNodeList placemarkList = kml.SelectNodes("//*[local-name()='Placemark']/*[local-name()='Point']/*[local-name()='coordinates']", NsMgr);

            IList<Tuple<double, double, double>> returnList = new List<Tuple<double, double, double>>();

            // we just assume that a route has more than 3 placemarks
            if (placemarkList.Count < 3)
            {
                try
                {
                    XmlNode tmpNode = kml.SelectSingleNode("//*[local-name()='LineString']/*[local-name()='coordinates']", NsMgr);

                    String[] coordArray = ChopCoordLineString(tmpNode.InnerText);

                    foreach (String item in coordArray)
                    {
                        String[] tmp = item.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                        returnList.Add(new Tuple<double, double, double>(Convert.ToDouble(tmp[0], numberFormatUS), Convert.ToDouble(tmp[1], numberFormatUS), Convert.ToDouble(tmp[2], numberFormatUS)));
                    }

                    return returnList;
                }
                catch
                {
                    return null;
                }
            }
            
            
            foreach (XmlNode item in placemarkList)
            {
                String[] tmp = item.InnerText.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                returnList.Add(new Tuple<double, double, double>(Convert.ToDouble(tmp[0], numberFormatUS), Convert.ToDouble(tmp[1], numberFormatUS), Convert.ToDouble(tmp[2], numberFormatUS)));
            }

            return returnList;
        }

        /// <summary>
        /// Get a list containing timestamps for each placemark or null otherwise
        /// </summary>
        public IList<DateTime> GetTimeStampList()
        {
            XmlNodeList placemarkList = kml.SelectNodes("//*[local-name()='Placemark']/*[local-name()='TimeStamp']/*[local-name()='when']", NsMgr);

            if (placemarkList.Count < 1)
            {
                return null;
            }
            
            IList<DateTime> returnList = new List<DateTime>();
            foreach (XmlNode item in placemarkList)
            {
                returnList.Add(DateTime.Parse(item.InnerText));
            }

            return returnList;
        }
    }
}